﻿(function (H) {

    // create shortcuts
    var defaultOptions = H.getOptions(),
		defaultPlotOptions = defaultOptions.plotOptions,
		seriesTypes = H.seriesTypes;

    // Trendline functionality and default options.
    defaultPlotOptions.trendline = H.merge(defaultPlotOptions.line, {

        marker: {
            enabled: false
        },

        tooltip: {
            valueDecimals: 2
        }
    });

    seriesTypes.trendline = H.extendClass(seriesTypes.spline, {

        type: 'trendline',
        animate: null,
        requiresSorting: false,
        processData: function () {
            var data;

            if (this.linkedParent) {
                data = [].concat(this.linkedParent.options.data)
                this.setData(this.runAlgorithm(), false);
            }

            H.Series.prototype.processData.call(this);
        },
        runAlgorithm: function () {

            var xData = this.linkedParent.xData,
				yData = this.linkedParent.yData,
				periods = this.options.periods || 100,		// Set this to what default? should be defaults for each algorithm.
				algorithm = this.options.algorithm || 'linear';

            return this[algorithm](xData, yData, periods);
        },


        /* Function that uses the calcMACD function to return the MACD line.
		 * 
		 * @return : the first index of the calcMACD return, the MACD.
		**/
        MACD: function (xData, yData, periods) {

            return calcMACD(xData, yData, periods)[0];
        },

        /* Function that uses the global calcMACD.
		 * 
		 * @return : the second index of the calcMACD return, the signalLine.
		**/
        signalLine: function (xData, yData, periods) {

            return calcMACD(xData, yData, periods)[1];
        },

        /* Function using the global SMA function.
		 * 
		 * @return : an array of SMA data.
		**/
        MA: function (xData, yData, periods) {

            return SMA(xData, yData, periods);
        },


        /* Function using the global EMA function.
		 * 
		 * @return : an array of EMA data.
		**/
        EMA: function (xData, yData, periods) {

            return EMA(xData, yData, periods);
        },

        /* Function that uses the global linear function.
		 *
		 * @return : an array of EMA data
		**/
        linear: function (xData, yData, periods) {

            return linear(xData, yData, periods);
        }

    });

    // Setting default options for the Histogram type.
    defaultPlotOptions.histogram = H.merge(defaultPlotOptions.column, {

        borderWidth: 0,

        tooltip: {
            valueDecimals: 2
        }

    });


    seriesTypes.histogram = H.extendClass(seriesTypes.column, {

        type: 'histogram',
        animate: null,
        requiresSorting: false,
        processData: function () {
            var data;

            if (this.linkedParent) {
                data = [].concat(this.linkedParent.options.data)
                this.setData(this.runAlgorithm(), false);
            }

            H.Series.prototype.processData.call(this);
        },

        runAlgorithm: function () {

            var xData = this.linkedParent.xData,
				yData = this.linkedParent.yData,
				periods = this.options.periods || 100,		// Set this to what default? should be defaults for each algorithm.
				algorithm = this.options.algorithm || 'histogram';

            return this[algorithm](xData, yData, periods);
        },


        histogram: function (xData, yData, periods) {

            return calcMACD(xData, yData, periods)[2];
        },

    });


    // Global functions.

    /* Function that calculates the MACD (Moving Average Convergance-Divergence).
	 *
	 * @param yData : array of y variables.
	 * @param xData : array of x variables.
	 * @param periods : The amount of "days" to average from.
	 * @return : An array with 3 arrays. (0 : macd, 1 : signalline , 2 : histogram) 
	**/
    function calcMACD(xData, yData, periods) {

        var chart = this,
			shortPeriod = 12,
			longPeriod = 26,
			signalPeriod = 9,
			shortEMA,
			longEMA,
			MACD = [],
			xMACD = [],
			yMACD = [],
			signalLine = [],
			histogram = [];


        // Calculating the short and long EMA used when calculating the MACD
        shortEMA = EMA(xData, yData, 12);
        longEMA = EMA(xData, yData, 26);

        // subtract each Y value from the EMA's and create the new dataset (MACD)
        for (var i = 0; i < shortEMA.length; i++) {

            if (longEMA[i][1] == null) {

                MACD.push([xData[i], null]);

            } else {
                MACD.push([xData[i], (shortEMA[i][1] - longEMA[i][1])]);
            }
        }

        // Set the Y and X data of the MACD. This is used in calculating the signal line.
        for (var i = 0; i < MACD.length; i++) {
            xMACD.push(MACD[i][0]);
            yMACD.push(MACD[i][1]);
        }

        // Setting the signalline (Signal Line: X-day EMA of MACD line).
        signalLine = EMA(xMACD, yMACD, signalPeriod);

        // Setting the MACD Histogram. In comparison to the loop with pure MACD this loop uses MACD x value not xData.
        for (var i = 0; i < MACD.length; i++) {

            if (MACD[i][1] == null) {

                histogram.push([MACD[i][0], null]);

            } else {

                histogram.push([MACD[i][0], (MACD[i][1] - signalLine[i][1])]);

            }
        }

        return [MACD, signalLine, histogram];
    }

    /**
	 * Calculating a linear trendline.
	 * The idea of a trendline is to reveal a linear relationship between 
	 * two variables, x and y, in the "y = mx + b" form.
	 * @param yData : array of y variables.
	 * @param xData : array of x variables.
	 * @param periods : Only here for overloading purposes.
	 * @return an array containing the linear trendline. 
	**/
    function linear(xData, yData, periods) {

        var lineData = [],
				step1,
				step2 = 0,
				step3 = 0,
				step3a = 0,
				step3b = 0,
				step4 = 0,
				step5 = 0,
				step5a = 0,
				step6 = 0,
				step7 = 0,
				step8 = 0,
				step9 = 0;


        // Step 1: The number of data points.
        step1 = xData.length;

        // Step 2: "step1" times the summation of all x-values multiplied by their corresponding y-values.
        // Step 3: Sum of all x-values times the sum of all y-values. 3a and b are used for storing data.
        // Step 4: "step1" times the sum of all squared x-values.
        // Step 5: The squared sum of all x-values. 5a stores data.
        // Step 6: Equation to calculate the slope of the regression line.
        // Step 7: The sum of all y-values.
        // Step 8: "step6" times the sum of all x-values (step5).
        // Step 9: The equation for the y-intercept of the trendline.
        for (var i = 0; i < step1; i++) {
            step2 = (step2 + (xData[i] * yData[i]));
            step3a = (step3a + xData[i]);
            step3b = (step3b + yData[i]);
            step4 = (step4 + Math.pow(xData[i], 2));
            step5a = (step5a + xData[i]);
            step7 = (step7 + yData[i]);
        }
        step2 = (step1 * step2);
        step3 = (step3a * step3b);
        step4 = (step1 * step4);
        step5 = (Math.pow(step5a, 2));
        step6 = ((step2 - step3) / (step4 - step5));
        step8 = (step6 * step5a);
        step9 = ((step7 - step8) / step1);

        // Step 10: Plotting the trendline. Only two points are calulated.
        // The starting point.
        // This point will have values equal to the first X and Y value in the original dataset.
        lineData.push([xData[0], yData[0]]);

        // Calculating the ending point.
        // The point X is equal the X in the original dataset.
        // The point Y is calculated using the function of a straight line and our variables found.
        step10 = ((step6 * xData[step1 - 1]) + step9);
        lineData.push([(xData[step1 - 1]), step10]);

        return lineData;
    }


    /* Function based on the idea of an exponential moving average.
	 * 
	 * Formula: EMA = Price(t) * k + EMA(y) * (1 - k)
	 * t = today, y = yesterday, N = number of days in EMA, k = 2/(2N+1)
	 *
	 * @param yData : array of y variables.
	 * @param xData : array of x variables.
	 * @param periods : The amount of "days" to average from.
	 * @return an array containing the EMA.	
	**/
    function EMA(xData, yData, periods) {

        var t,
			y = false,
			n = periods,
			k = (2 / (n + 1)),
			ema,	// exponential moving average.
			emLine = [],
			periodArr = [],
			length = yData.length,
			pointStart = xData[0];

        // loop through data
        for (var i = 0; i < length; i++) {


            // Add the last point to the period arr, but only if its set.
            if (yData[i - 1]) {
                periodArr.push(yData[i]);
            }


            // 0: runs if the periodArr has enough points.
            // 1: set currentvalue (today).
            // 2: set last value. either by past avg or yesterdays ema.
            // 3: calculate todays ema.
            if (n == periodArr.length) {


                t = yData[i];

                if (!y) {
                    y = arrayAvg(periodArr);
                } else {
                    ema = (t * k) + (y * (1 - k));
                    y = ema;
                }

                emLine.push([xData[i], y]);

                // remove first value in array.
                periodArr.splice(0, 1);

            } else {

                emLine.push([xData[i], null]);
            }

        }

        return emLine;
    }

    /* Function based on the idea of a simple moving average.
	 * @param yData : array of y variables.
	 * @param xData : array of x variables.
	 * @param periods : The amount of "days" to average from.
	 * @return an array containing the SMA.	
	**/
    function SMA(xData, yData, periods) {
        var periodArr = [],
			smLine = [],
			length = yData.length,
			pointStart = xData[0];

        // Loop through the entire array.
        for (var i = 0; i < length; i++) {

            // add points to the array.
            periodArr.push(yData[i]);

            // 1: Check if array is "filled" else create null point in line.
            // 2: Calculate average.
            // 3: Remove first value.
            if (periods == periodArr.length) {

                smLine.push([xData[i], arrayAvg(periodArr)]);
                periodArr.splice(0, 1);

            } else {
                smLine.push([xData[i], null]);
            }
        }
        return smLine;
    }

    /* Function that returns average of an array's values.
	 *
	**/
    function arrayAvg(arr) {
        var sum = 0,
			arrLength = arr.length,
			i = arrLength;

        while (i--) {
            sum = sum + arr[i];
        }

        return (sum / arrLength);
    }

}(Highcharts));